using System;
using System.Net;
using System.Security;
using System.Web;
using System.Xml;

/*	-----------------------------------------------------------------------	
	Copyright:	umlungu consulting (Pty) ltd
	Author:		Alan Benington	
	Started:	2005-05-15	
	Status:		release	
	Version:	2.0.0
	Buidl:		20071103
	License:	GNU General Public License
	-----------------------------------------------------------------------	*/
	
/*	-----------------------------------------------------------------------	
	Development Notes:
	==================
	20070524:	Modification for .Net 2.0
	20071103:	Added stuff for handling xml blogs, ie:
				- call to UpdateFormXml method of profile (like UpdateProfile)
				- call parameter type formxml
	20071121:	Extended handling of xml blogs to xhtml blogs that can include 
				multiple html elements.
	-----------------------------------------------------------------------	*/
	
namespace umlungu.engineX.webX {
	/// <summary>
	/// Declare delegate types for processing handler results:
	/// </summary>
	public delegate void ProcessServiceResult(XmlDocument xmldom, profileX profile, contentX content);
	public delegate void ProcessFormResult(profileX profile, contentX content);
	public delegate string SoapProxyCall(string soapOperation, object[] parameters);

	/// <summary>
	/// umlungu engineX web framework, handler class.
	/// </summary>
	public class handlerX : System.Web.UI.Page
	{
		#region Invisible properties
		private DateTime starttime;
		private string siteid = "";
		private bool debug;
		private XmlDocument xmldom, xmldef;
		private errorX errobj;	// error object
		private remoteX remobj;	// remote object
		private profileX profile;
		private contentX content;
		private bool want_repeat = false;
		private int repeat = 1, nrepeat = 0;
		private string type, name, path, errpath, procname;
		private bool isForm, isService, isProcess;
		private static string deftype = "form";
		// private memebers to pass to delegates for Soap proxies
		private string soapMethod;
		private object[] soapParameters;
		#endregion

		#region Constants
		#endregion
		
		#region Visible properties
		public string Process	{	get	{ return procname; }	}
		#endregion

		#region Constructors/Destructors
		/// <summary>Constructor</summary>
		public handlerX() {
			starttime = DateTime.Now;	// start timer
			debug = false;
			initialise(deftype, getQueryParam(deftype));
		}
		public handlerX(bool wantdebug) {
			starttime = DateTime.Now;	// start timer
			debug = wantdebug;
			initialise(deftype, getQueryParam(deftype));
		}
		public handlerX(string thistype, bool wantdebug) {
			starttime = DateTime.Now;	// start timer
			debug = wantdebug;
			initialise(thistype, getQueryParam(thistype));
		}
		public handlerX(string thistype, string thisname, bool wantdebug, string thissite) {
			starttime = DateTime.Now;	// start timer
			debug = wantdebug;
			siteid = thissite;
			initialise(thistype, thisname);
		}
		#endregion
		
		#region Public methods
		/// <summary>Method description</summary>
		public virtual void Handle() {
			_Debug("Handle");
			try {
				content.FromSession();
				procname = getQueryParam("process");
				isProcess = (procname != "")? true : false;
				_Debug("Handle", "procname:", procname);
				loadDefiniton();
				_Debug("Handle", "after loadDefiniton");
				handle();
				_Debug("Handle", "after handle");
			} catch (exceptionX except) {
				_Debug("Handle", except.Xml.DocumentElement.OuterXml);
				content.SetXml(except.Xml);
			} catch (SecurityException except) {
				exceptionX exceptX = new exceptionX("handlerX_handle_system", "security_error", except);
				_Debug("Handle", exceptX.Xml.DocumentElement.OuterXml);
				content.SetXml(exceptX.Xml);
			} catch (WebException except) {
				exceptionX exceptX = new exceptionX("handlerX_handle_system", "web_error", except);
				_Debug("Handle", exceptX.Xml.DocumentElement.OuterXml);
				content.SetXml(exceptX.Xml);
			} catch (System.Web.Services.Protocols.SoapException except) {
				exceptionX exceptX = new exceptionX("handlerX_handle_system", "soap_error", except);
				_Debug("Handle", exceptX.Xml.DocumentElement.OuterXml);
				content.SetXml(exceptX.Xml);
			} catch (Exception except) {
				exceptionX exceptX = new exceptionX("handlerX_handle_system", "system_error", except);
				_Debug("Handle", exceptX.Xml.DocumentElement.OuterXml);
				content.SetXml(exceptX.Xml);
			}
		}
		/// <summary>
		/// Method description
		/// </summary>
		public virtual void Initialise(string type, string name) {
			_Debug("Initialise", "type:", type, " name:", name);
			initialise(type, name);
		}
		/// <summary>
		/// Method description
		/// </summary>
		public void ProcessResult(ProcessServiceResult processService) {
			// Calling the delegate:
			processService(xmldom, profile, content);
		}
		public void ProcessResult(ProcessFormResult processForm) {
			// Calling the delegate:
			processForm(profile, content);
		}
		public string SoapCall(SoapProxyCall callSoap) {
			// Calling the delegate:
			return(callSoap(soapMethod, soapParameters));
		}
		#endregion

		#region Protected methods
		/// <summary>
		/// Method: _Soap
		///		- for the dynamic handling of soap calls
		///		- redefined in the service handler page
		///		- the service handler page calls the soap delegate (located in the proxy class)
		/// </summary>
		protected virtual string _Soap(string opName) {
			return(opName);	
		}
		/// <summary>
		/// Method: ProcessXml
		/// 	- Processes the xml of a successful result (for a service)
		///		- This function can be redefined in the service handler page
		///		- Passes the (loaded) xml and the profile objects
		/// </summary>
		protected virtual void _Process(XmlDocument xmldom, profileX profile, contentX content) {
		}
		/// <summary>
		/// Method: Process
		/// 	- Processes a successful result (for a form)
		///		- This function can be redefined in the form handler page
		///		- Passes profile object
		/// </summary>
		protected virtual void _Process(profileX profile, contentX content) {
		}
		/// <summary>
		/// Method description
		/// </summary>
		protected void _Debug(string label, params string[] outparam) {
			if (debug) {
				Response.Write("\n<!-- handlerX.");
				Response.Write(label);
				Response.Write("::\n");
				for (int i=0; i < outparam.Length; i++)
					Response.Write(outparam[i]);
				Response.Write("\n");
				Response.Write(getElapsed());
				Response.Write("-->\n");
			}
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getElapsed() {
			DateTime thistime = DateTime.Now;
			TimeSpan tspan = new TimeSpan(thistime.Ticks - starttime.Ticks);
			return(tspan.TotalMilliseconds.ToString());
		}
		#endregion

		#region Private methods
		/// <summary>
		/// initialise:: Initialise object ...
		/// </summary>
		private void initialise(string thistype, string thisname) {
			if (thisname == "")
				throw(new exceptionX("handlerX_initialise_name", "handler name not specified"));
			type = (thistype != "")? thistype : "form";	// form handler by default
			isService = (type == "service")? true : false;
			isForm = (type == "form")? true : false;
			name = thisname;
			// create required objects
			xmldef = new XmlDocument();
			errobj = new errorX();
			profile = new profileX(this, false, siteid);	// NB: false because cannot use Response in this context
			content = new contentX(this, debug);
			if (isService) {
				remobj = new remoteX(this, debug);
				xmldom = new XmlDocument();
			}
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void loadDefiniton() {
			path = getHandlerPath();
			errpath = getErrorPath();
			if (name != "" && xmldef != null) {
				try {
					xmldef.Load(Server.MapPath(String.Concat(path, name, ".xml")));
					errobj.FileName = Server.MapPath(String.Concat(errpath, getDefAttr("file", "errors")));
				} catch (System.Exception e) {
					throw(new exceptionX("handlerX_loadDefiniton", String.Concat("unable to load defintion file ('", Server.MapPath(String.Concat(path, name, ".xml")), "')"), e));
				}
			}
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getHandlerPath() {
			string path = Request.ServerVariables["PATH_INFO"];
			int last = path.LastIndexOf("/");
			return(String.Concat(path.Substring(0,last), "/handlers/", type, "s/"));
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getErrorPath() {
			string path = Request.ServerVariables["PATH_INFO"];
			int last = path.LastIndexOf("/");
			return(String.Concat(path.Substring(0,last), "/handlers/"));
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getFormParam(string thisparam) {
			return(getFormParam(thisparam, ""));
		}
		private string getFormParam(string thisparam, string defval) {
			string thisparamstr = defval;
			string thisquery = Request.Form[thisparam];
			if (thisquery != "" && thisquery != null)
				thisparamstr = Request.Form[thisparam];
			return(thisparamstr);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getQueryParam(string thisparam) {
			return(getQueryParam(thisparam, ""));
		}
		private string getQueryParam(string thisparam, string defval) {
			string thisparamstr = defval;
			string thisquery = Request.QueryString[thisparam];
			if (thisquery != "" && thisquery != null)
				thisparamstr = Request.QueryString[thisparam];
			return(thisparamstr);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void setError(int errcode) {
			_Debug("setError", "errcode:", errcode.ToString());
			setError(errcode.ToString());
		}
		private void setError(int errcode, System.Exception except) {
			_Debug("setError", except.Message, " - ", except.StackTrace);
			setError(errcode.ToString());
		}
		private void setError(string errcode) {
			errobj.Respond(errcode, content);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void handle() {
			_Debug("handle", "updateprofile:", getDefTrue("updateprofile").ToString());
			if (getDefTrue("updateprofile"))
				profile.UpdateProfile(true);
			/*
			_Debug("handle", "updateformxml:", getDefTrue("updateformxml").ToString());
			if (getDefTrue("updateformxml"))
				profile.UpdateFormXml();
			*/
			_Debug("handle", "updateformxml:", getDefAttr("updateformxml", ""));
			profile.UpdateFormXml(getDefAttr("updateformxml", ""));
			if (isService)
				handleService();
			else
				handleForm();
		}
		/// <summary>
		/// reHandle:: try and handle another url, assuming definition in this file
		/// </summary>
		private void reHandle(string prcname) {
			procname = prcname;
			handle();
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void handleService() {
			getDefRepeat();
			for (nrepeat = 0; nrepeat < repeat; nrepeat++) {
				_Debug("handleService", "before callurl");
				string thisxml = callurl(remobj);
				_Debug("handleService", "after callurl");
				_Debug("handleService", "thisxml:", thisxml);	// too much information?
				try {
					xmldom.LoadXml(thisxml);
				} catch(System.Exception e)	{ 
					setError(102, e); 
				}
				_Debug("handleService", "before processService");
				processService();
				_Debug("handleService", "after processService");
			}
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void handleForm() {
			processForm();
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string callurl(remoteX remote) {
			_Debug("callurl");
			string thisroot = getDefault("url");
			string thistype = getDefAttr("type", "call");
			string thisbase = String.Concat(thisroot, getDefElem("name", "call"), getDefault("ext"));
			bool wantflds = getDefTrue("fields", "call");
			//_Debug("callurl", "wantflds:", wantflds.ToString());
			string thisparam = getDefParamString("parameter", "call");
			//_Debug("callurl", "thisparam", thisparam);
			string checkname = getDefElem("check", "call");
			string checkfld = getDefAttr("field", "call/check");
			bool checkincr = getDefTrue("incr", "call/check");
			// get service name to be used
			string thisserv = getDefAttr("service", "call");
			string thisservname = (thisserv != "")? thisserv : name;
			// get process name to be used
			string thisproc = getDefAttr("process", "call");
			string thisprocname = (thisproc != "")? thisproc : procname;
			_Debug("callurl", "thisbase:", thisbase, " thistype:", thistype, " paramstring:", thisparam);
			switch (thistype) {
				case "soap":
					/* if using DynWSLib...
					thisprocname = (getDefAttr("operation", "call") == "")? thisprocname : getDefAttr("operation", "call");
					remobj.SetSoap(thisbase, getDefElem("name", "call"), thisprocname, getDefParamArray("parameter", "call"));
					*/
					setSoap(thisprocname);
					break;
				case ".net":	// operation is either explicitly defined or same as process name
					string thisop = (getDefAttr("operation", "call") == "")? thisprocname : getDefAttr("operation", "call");
					remobj.SetUrl(thistype, thisbase, thisservname, thisop, thisparam, wantflds, checkname, checkfld, checkincr);
					break;
				case "test":
					remobj.SetUrl(getDefElem("testurl", "call"));
					break;
				case "umlungux":
					remobj.SetUrl(thistype, thisbase, thisservname, thisprocname, thisparam, wantflds, checkname, checkfld, checkincr);
					break;
				case "umlungu":
				default:
					remobj.SetUrl(thistype, thisbase, thisservname, thisprocname, thisparam, wantflds, checkname, checkfld, checkincr);
					break;
			}
			// Now call the remote object to get the data via soap / get / post
			if (thistype == "soap")
				// if using DynWSLib... return(remobj.Soap());
				return(_Soap(soapMethod));
			else
				return((thistype == ".net")? remobj.Post() : remobj.Get());
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void setSoap(string procname) {
			soapMethod = (getDefAttr("operation", "call") == "")? procname : getDefAttr("operation", "call");
			soapParameters = getDefParamArray("parameter", "call");
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void processService() {
			bool want_process;
			string restyp = getDefAttr("type", "results");
			string resval = getDefAttr("value", "results");
			string resdef = getDefAttr("default", "results");
			_Debug("processService", "resval:", resval);

			// Now check if this is OK
			XmlNode testnode = xmldom.SelectSingleNode(resval);
			if (testnode != null) {
				string thisres = testnode.InnerText;
				// first locate the node that expresses the correct result
				XmlNode result = xmldef.SelectSingleNode(String.Concat("//process[@name='", procname, "']/results/result[@value='", thisres, "']"));
				// try process-common results
				if (result == null) {
					_Debug("processService", "xmldom:", xmldom.OuterXml);
					result = xmldef.SelectSingleNode(String.Concat("//handler/results/result[@value='", thisres, "']"));
					// use default result
					if (result == null)
						result = xmldef.SelectSingleNode(String.Concat("//process[@name='", procname, "']/results/result[@value='", resdef, "']"));
					want_process = false;
				} else {
					want_process = true;
				}
				// Now deal with the result - process the xml and return the result
				if (result != null) {
					// Pass the xml to be manipulated (can be redefined in calling asp)
					if (want_process)
						_Process(xmldom, profile, content);
					setResult(result);
				} else {
					setError(103);
				}
			} else {
				setError(104);
			}
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void processForm() {
			string thisval;
			string restyp = getDefAttr("type", "results");
			string resval = getDefAttr("value", "results");
			string resdef = getDefAttr("default", "results");
			
			switch (restyp) {
				case "query":
					thisval = getQueryParam(resval, resdef);
					break;
				case "field":
					thisval = getFormParam(resval, resdef);
					break;
				case "profile":
					thisval = profile.GetProfileValue(resval);
					if (thisval == "")
						thisval = resdef;
					break;
				case "none":
				default:
					thisval = resdef;
					break;
				}
			// now get the result associated with the value
			if (thisval != "") {
				XmlNode result = xmldef.SelectSingleNode(String.Concat("//process[@name='", procname, "']/results/result[@value='", thisval, "']"));
				// Now deal with the result - process the xml and return the result
				if (result != null) {
					// Pass the xml to be manipulated (can be redefined in calling asp)
					_Process(profile, content);
					setResult(result);
				} else {
					setError(105);
				}
			} else {
				setError(106);
			}
		}
		/// <summary>
		/// Method: SetError
		/// 	- Sets the error by the appropriate action defined in the xml
		/// </summary>
		private void setResult(XmlNode resnode) {
			_Debug("setResult", "resnode:", resnode.OuterXml);
			XmlElement reselem = (XmlElement) resnode;
			string restype = reselem.GetAttribute("type");
			string rescode = reselem.GetAttribute("value");
			_Debug("setResult", "restype:", restype);
			switch (restype) {
				case "contentxml":
					content.Set(content.Xml);
					break;
				case "xml":
					content.SetXml(xmldom);
					break;
				case "process":	// Check for recursive handling - process within this service
					reHandle(resnode.InnerText);
					break;
				case "service":	// Check for recursive handling - new service/process
					name = resnode.InnerText;
					loadDefiniton();
					reHandle(reselem.GetAttribute("process"));
					break;
				case "form":	// Check for recursive handling - new form/process
					// string resproc = resnode.GetAttribute("process");
					type = "form";
					isService = false;
					isForm = true;
					name = resnode.InnerText;
					loadDefiniton();
					reHandle(reselem.GetAttribute("process"));
					break;
				case "page":
				default:
					content.SetPage(reselem.GetAttribute("sect"), reselem.GetAttribute("sub"), reselem.GetAttribute("proc"), reselem.GetAttribute("page"));
					break;
				}
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getDefAttr(string attrname, string nodename) {
			string selstr = (isProcess)? String.Concat("//process[@name='", procname, "']/") : "//";
			selstr = String.Concat(selstr, ((nodename != "")? String.Concat(nodename, "/@", attrname) : String.Concat("@", attrname)));
			XmlNode thisnode = xmldef.SelectSingleNode(selstr);
			return((thisnode != null)? thisnode.InnerText : "");
		}
		/// <summary>
		/// Method description
		/// </summary>
		private bool getDefTrue(string attrname) {
			return(getDefTrue(attrname, ""));
		}
		private bool getDefTrue(string attrname, string nodename) {
			return((getDefAttr(attrname, nodename) == "yes")? true : false);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getDefElem (string nodename, string parentname) {
			string selstr = (isProcess) ? String.Concat("//process[@name='", procname, "']/") : "//";
			if (parentname != "")
				selstr = String.Concat(selstr, parentname, "/");
			selstr = String.Concat(selstr, nodename);
			XmlNode thisnode = xmldef.SelectSingleNode(selstr);
			return((thisnode != null)? thisnode.InnerText : "");
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getDefParamString(string nodename, string parentname) {
			string thisval, thisname, paramstr = "";
			string selstr = getDefSelect(nodename, parentname);
			bool first = true;
			foreach (XmlNode thisnode in xmldef.SelectNodes(selstr)) {
				if (!first)
					paramstr = String.Concat(paramstr, "&");
				//_Debug("getDefParamString", "thisnode:", thisnode.InnerText);
				thisval = getParameter((XmlElement) thisnode, out thisname);
				paramstr = String.Concat(paramstr, thisname, "=", thisval); 
				first = (first)? false : first;
			}
			// paramstr = encodeURI(paramstr);	// NB dependancy on script engine!
			return(paramstr);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getDefSelect(string nodename, string parentname) {
			string selstr = (isProcess) ? "//process[@name='" + procname + "']/" : "//";
			if (parentname != "")
				selstr = String.Concat(selstr, parentname, "/");
			return(String.Concat(selstr, nodename));
		}
		/// <summary>
		/// Method description
		/// </summary>
		private object[] getDefParamArray(string nodename, string parentname) {
			string thisname, thisval, selstr = getDefSelect(nodename, parentname);
			XmlNodeList nodeList = xmldef.SelectNodes(selstr);
			Array paramArray = Array.CreateInstance(typeof(object), nodeList.Count);
			int i = 0;
			foreach (XmlNode thisnode in nodeList) {
				thisval = getParameter((XmlElement) thisnode, out thisname);
				paramArray.SetValue(thisval, i);
				i++;
			}
			return((object[]) paramArray);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getParameter(XmlElement elem, out string name) {
			string thisval = "";
			name = elem.GetAttribute("name");
			string thisupdp = elem.GetAttribute("updateprofile");
			string thistype = elem.GetAttribute("type");
			string thisdefv = elem.GetAttribute("default");
			string thisrept = elem.GetAttribute("repeat");
			string thisencd = elem.GetAttribute("encode");
			string thisitem = (want_repeat && thisrept == "yes")? String.Concat(elem.InnerText, "_", (nrepeat+1).ToString()) : elem.InnerText;
			switch (thistype) {
				case "profile":	// Get value from profile
					thisval = profile.GetProfileValue(thisitem);
					break;
				case "query":	// Get value from QueryString
					thisval = Request.QueryString[thisitem];
					break;
				case "form":	// Get value from Form field (used when only want one from many)
					thisval = Request.Form[thisitem];
					break;
				case "file":	// Get value from Request.Files
					System.Web.HttpPostedFile thisfile = Request.Files.Get(thisitem);
					int last = thisfile.FileName.LastIndexOf("\\");
					last = (last <= 0)? 0 : last+1;
					thisval = thisfile.FileName.Substring(last);
					break;
				case "formxml":	// Get value from profile formXml
					thisval = profile.FormXml.InnerXml;
					break;
				// Get profile properties:
				case "token":		thisval = profile.Token;		break;
				case "group":		thisval = profile.Group;		break;
				case "name":		thisval = profile.Name;			break;
				case "username":	thisval = profile.Username;		break;
				case "firstname":	thisval = profile.Firstname;	break;
				case "surname":		thisval = profile.Surname;		break;
				case "email":		thisval = profile.Email;		break;
				case "telno":		thisval = profile.Telno;		break;
				// Get value from node directly
				case "value":
				default:			thisval = elem.InnerText;		break;
				}
			if (thisdefv != "" && thisval == "")
				thisval = thisdefv;
			if (thisval != "" && thisencd == "yes") {	// htmlencode
				thisval = thisval.Replace("+", "%2B");	// '+' not handled well
				thisval = thisval.Replace(" ", "%20");
				}
			if (thisupdp == "yes")
				profile.AddFldtoProfile(name, thisval);
			return(thisval);
		}
		/// <summary>
		/// Method description
		/// </summary>
		private void getDefRepeat() {
			string rept = getDefElem("repeat", "call");
			want_repeat = (rept == "")? false : true;
			repeat = (rept == "")? 1 : Convert.ToInt32(profile.GetProfileValue(rept));
		}
		/// <summary>
		/// Method description
		/// </summary>
		private string getDefault(string nodename) {
			XmlNode thisnode = xmldef.SelectSingleNode("//defaults/" + nodename);
			return((thisnode != null)? thisnode.InnerText : "");
		}
		#endregion

		#region Redundant methods (?)
		/*
		private void LoadFunction () {
			// re-define the prototype for the ProcessXml function from the definition
			var thisfunct = getDefElem("function", "results");
			if (thisfunct != "") {
				if (isService)
					ProcessXml = new Function("xmldom", "profile", thisfunct);
				else
					Process = new Function("profile", thisfunct);;
				}
			else {
				thisfunct = getDefAttr("function", "results");
				if (thisfunct != "") {
					if (isService)
						ProcessXml = eval(thisfunct);
					else
						Process = eval(thisfunct);
					}
				}
		}
		*/
		#endregion
	}
}
